//
// conversion.cpp
// libprotea
// Tools Library for the Protea Project / Data conversion functions for Fields
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include "../defines.h"
#include <stdlib.h>
#include <math.h>
#include "fields.h"
#include "../Buffer/Buffer.h"
#include "../AppHandler/AppInterface.h"
#include "../OSDep/utf8.h"
#include "dates.h"
#include "math64.h"
#include "field_defns.h"

#include "../defns.h"
using namespace ProteaApp1;

const double EPSILON = 0.00000001; // Duplicated from somewhere else

// Time forward declarations
const long HH_LENGTH = 256;
const char DECIMAL_POINT_CHAR = '.';
const char DURATION_SEP = ':';
const char ALT_DECIMAL_POINT = '.';

long BINARY( char* data, int len ) {
	char temp[ 256 ];

	memcpy( temp, data, len );
	temp[len] = 0;

	return atol( temp );
};




long DBINARY( char* data, int len ) {
	return BINARY( data, len );
};



int ASCII( int time, long base, char* result ) {
	if (base == 10)
		return sprintf( result, "%d", time );
	else
		return sprintf( result, "%x", time );
};

int DASCII( int time, long base, char* result ) {
	return ASCII( time, base, result );
};



long FIXT( double value ) {
	double fractional, integer;
	fractional = modf( value, &integer );
	return (long)integer;
};




// Only works for positive numbers!!!
long FIXR( double value ) {
	double fractional, integer;
	fractional = modf( value, &integer );
	if (fractional > 0.5)
		return (long)integer + 1;
	else
		return (long)integer;
};













				 
bool XCField::conversion_tbl_lookup( long stype ) {
	const tdata_type_defnition *source_dd = &data_type_defns[ stype ];
	return (source_dd->valid_conv[ type ] == '1');
};




void XCField::string_or_numeric_move( const XCField* source, bool MoveBlanks ) {
	INFUNC( XCField::string_or_numeric_move, owner->APPDIC )

#define TYPEMOVE( x, y ) x * 256 + y

	switch( TYPEMOVE( source->type, type ) ) {
		// Support for new literal value data type
		case TYPEMOVE( LITERAL_VAL, LITERAL_STRING ):
			if (source->GetDecPlaces() != GetDecPlaces())
				RAISEFIELDERROR( ERR_INV_LIT_SCR_TAB_MATCH, GetFieldNumber() );

		case TYPEMOVE( LITERAL_VAL, STRING ):
			if (source->GetLong() != NOT_IN_LIT_GROUP) {
				source = owner->APPDIC->FromLitGroup( source->GetLong(), source->GetDecPlaces() );
				if (!source)
					RAISEFIELDERROR( ERR_INV_LIT_VAL_FOR_GROUP, GetFieldNumber() );
			} else {
				// Blank out whole string
				memset( buffer_offset, 0, source->size );
				break;
			};

			// LET FALL THROUGH TO STRING MOVE

		// String to string moves
		case TYPEMOVE( BIG_STRING, BIG_STRING ):
		case TYPEMOVE( BIG_STRING, STRING ):
		case TYPEMOVE( BIG_STRING, VAR_STRING ):
		case TYPEMOVE( STRING, BIG_STRING ):
		case TYPEMOVE( STRING, STRING ):
		case TYPEMOVE( STRING, VAR_STRING ):
		case TYPEMOVE( ALPHA_STRING, BIG_STRING ):
		case TYPEMOVE( ALPHA_STRING, STRING ):
		case TYPEMOVE( ALPHA_STRING, ALPHA_STRING ):
		case TYPEMOVE( ALPHA_STRING, VAR_STRING ):
		case TYPEMOVE( DISPLAY_NUM, DISPLAY_NUM ):
		case TYPEMOVE( VAR_STRING, BIG_STRING ):
		case TYPEMOVE( VAR_STRING, STRING ):
		case TYPEMOVE( VAR_STRING, VAR_STRING ):
		case TYPEMOVE( LITERAL_STRING, STRING ):
		case TYPEMOVE( STRING, LITERAL_STRING ):
		case TYPEMOVE( LITERAL_STRING, LITERAL_STRING ):
			// If explicit blank, then blank out the target field.  If field has all
			// spaces, then don't do anything - ignore
			if (source->IsExplicitBlank()) {
				// Blank out whole string
				memset( buffer_offset, 0, source->size );
			} else if (!source->IsBlank() || MoveBlanks) {
//		printf( "Set field: %s value to %s\n", GetFieldName(), source->buffer_offset );
				truncated = !UTF8ToUTF8( source->buffer_offset, buffer_offset, allocsize, size );
//		printf( "  result=%s,  truncated:%s\n", buffer_offset, truncated?"Yes":"No" );
			};
			break;

		case TYPEMOVE( XCBOOL, XCBOOL ):
			// Rules for moving bool to bool:
			//	1.	If source field is not blank, move over first character (0 or 1)
			//	2.	If source field blank, leave whatever is in there already
			//	3.	Always check to see the result character is either 0 or 1
			if (!source->IsBlank()) {
				char bool_char = *source->buffer_offset;
				*buffer_offset = bool_char;
				if (bool_char != '0' && bool_char != '1')
					RAISEFIELDERROR_1PARAM( ERR_INV_CHAR, source->GetFieldNumber(), this );
			} else if (source->IsExplicitBlank()) {
				// Explicit blank is invalid...
				RAISEFIELDERROR_1PARAM( ERR_INV_CHAR, source->GetFieldNumber(), this );
			} else
				SetDefault();

			break;

		// Conversions from display numeric to single/double integer
		case TYPEMOVE( DISPLAY_NUM, S_INTEGER ):
		case TYPEMOVE( DISPLAY_NUM, D_INTEGER ): 
		case TYPEMOVE( DISPLAY_NUM, AUTONUMBER ): {
			// Display numeric fields are not allowed to be explicitly blank
			if (source->IsExplicitBlank()) {
				RAISEFIELDERROR( 1, source->GetFieldNumber() );
			} else {
				// NULL terminate
				char* chartemp = source->buffer_offset;
				chartemp[ source->size ] = 0;

				if (*chartemp) {
					if (type == S_INTEGER) {
						short int shorttemp = atoi( chartemp );
						memcpy( buffer_offset, &shorttemp, sizeof(short int) );
					} else {
						long temp = atol( chartemp );
						memcpy( buffer_offset, &temp, sizeof( long ) );
					};
				} else
					SetDefault();
			};
		};
			break;



		case TYPEMOVE( DISPLAY_NUM, DEC_FLOAT ):
		case TYPEMOVE( DISPLAY_NUM, DEC_DOUBLE ): {
			if (source->IsExplicitBlank()) {
				RAISEFIELDERROR( 1, source->GetFieldNumber() );
			} else {
				// NULL terminate
				char* chartemp = source->buffer_offset;
				chartemp[ source->size ] = 0;

				if (*chartemp) {
					if (type == DEC_FLOAT) {
						float shorttemp = (float)atof( chartemp );
						memcpy( buffer_offset, &shorttemp, sizeof(float) );
					} else {
						char* stop;

						double temp = strtod( chartemp, &stop );
						if (*stop == 0) {
							if (temp == HUGE_VAL) {
								RAISEFIELDERROR( ERR_OVERFLOW, GetFieldNumber() );
							} else if (temp == -HUGE_VAL)
								RAISEFIELDERROR( ERR_UNDERFLOW, GetFieldNumber() );

							memcpy( buffer_offset, &temp, sizeof( double ) );
							owner->APPDIC->ToolsTrace( TRC_FIELD, TLV_EVERYTHING, "Conversion value: %s--->%g", chartemp, temp );
							owner->APPDIC->ToolsTrace( TRC_FIELD, TLV_EVERYTHING, "Conversion value: %s--->%g", chartemp, buffer_offset );
						} else {
							owner->APPDIC->ToolsTrace( TRC_FIELD, TLV_LOW, "Invalid decimal digit\nOriginal string: '%s'\nError at: '%s'", chartemp, stop );
							RAISEFIELDERROR( ERR_INV_DEC_DIGIT, source->GetFieldNumber() );
						};
					};
				} else 
					SetDefault();
			};
		};
			break;


		case TYPEMOVE( S_INTEGER, DISPLAY_NUM ): {
			int len;
			len = snprintf( buffer_offset, GetLength()+1, "%d", source->GetShort() );
			if (size-len > 0)
				memset( buffer_offset + len, 0, size-len );

//			printf( "Field: %s:  Convert '%d' to '%s'\n", GetFieldName(), source->GetShort(), buffer_offset );
			break;
		};

		case TYPEMOVE( D_INTEGER, DISPLAY_NUM ): 
		case TYPEMOVE( AUTONUMBER, DISPLAY_NUM ): {
			int len;
			len = snprintf( buffer_offset, GetLength()+1, "%ld", source->GetLong() );
			if (size-len > 0)
				memset( buffer_offset + len, 0, size-len );
			break;
		};

		case TYPEMOVE( DEC_FLOAT, DISPLAY_NUM ):
		case TYPEMOVE( DEC_DOUBLE, DISPLAY_NUM ): {
			double value;

			if (source->type == DEC_FLOAT) {
				float f = source->GetFloat();
				value = f;
			} else
				value = source->GetDouble();

			char temp[ 80 ];
			char temp2[ 81 ];
			sprintf( temp, "%%%ld.%ldf", size, GetDecPlaces() );
			snprintf( temp2, 81, temp, value );

			memcpy( buffer_offset, temp2, size );
		};
			break;
			
		// Direct moves of numeric types
		case TYPEMOVE( S_INTEGER, S_INTEGER ):
		case TYPEMOVE( LITERAL_VAL, LITERAL_VAL ):
		case TYPEMOVE( D_INTEGER, D_INTEGER ):
		case TYPEMOVE( AUTONUMBER, AUTONUMBER ):
			memcpy( buffer_offset, source->buffer_offset, size );
			break;

		case TYPEMOVE( S_INTEGER, D_INTEGER ): 
		case TYPEMOVE( AUTONUMBER, D_INTEGER ): {
			long *temp = (long*)buffer_offset;
			short int *shorty = (short int*)source->buffer_offset;

			*temp = 0;
			*temp = *shorty;
		};
			break;

		case TYPEMOVE( D_INTEGER, S_INTEGER ): 
		case TYPEMOVE( AUTONUMBER, S_INTEGER ): {
			long *temp = (long*)source->buffer_offset;
			short int *shorty = (short int*)buffer_offset;

			*shorty = (short int)*temp;
		};
			break;

		case TYPEMOVE( D_INTEGER, DEC_DOUBLE ): 
		case TYPEMOVE( AUTONUMBER, DEC_DOUBLE ): {
			long *temp = (long*)source->buffer_offset;
			double *dest = (double*)buffer_offset;

			*dest = (double)*temp;
		};
			break;

		case TYPEMOVE( D_INTEGER, DEC_FLOAT ): 
		case TYPEMOVE( AUTONUMBER, DEC_FLOAT ): {
			long *temp = (long*)source->buffer_offset;
			float *dest = (float*)buffer_offset;

			*dest = (float)*temp;
		};
			break;

		case TYPEMOVE( DEC_FLOAT, DEC_FLOAT ):
		case TYPEMOVE( DEC_DOUBLE, DEC_DOUBLE ): 
			memcpy( buffer_offset, source->buffer_offset, size );
			break;

		// Conversion of a string to a literal value
		case TYPEMOVE( LITERAL_STRING, LITERAL_VAL ):
		case TYPEMOVE( BIG_STRING, LITERAL_VAL ):
		case TYPEMOVE( STRING, LITERAL_VAL ): 
		case TYPEMOVE( ALPHA_STRING, LITERAL_VAL ):{
			if (source->IsExplicitBlank()) {
				RAISEFIELDERROR( 1, source->GetFieldNumber() );
			} else {
				if (!source->IsBlank() || MoveBlanks) {
					long result = owner->APPDIC->ToLitGroup( source, dec_places );
					if (result != NOT_IN_LIT_GROUP || (source->IsBlank() && AllowBlankLiterals())) {
						// Set the literal value
						memcpy( buffer_offset, &result, sizeof( long ));
					} else {
						// Display an error message
						char* tempstr = new char[ 4000 ];
						char* p = tempstr;

						// Create the in clause...
						lit_group* group = owner->APPDIC->GetLitGroup( dec_places );
						int count = group->literals.count;
						lit_relate* relate = (lit_relate*)owner->APPDIC->FixOffset( group->literals.offset );
						while (count) {
							const char* value = owner->APPDIC->GetLiteral( relate->lit_number )->GetStringPtr();
							if (*value == 0)
								value = owner->APPDIC->GetCatalogMsg( ERR_BLANK_VALUE );

							if (count != group->literals.count) {
								if (count == 1)
									p += sprintf( p, ", or %s", value );
								else
									p += sprintf( p, ", %s", value );
							} else
								p += sprintf( p, "%s", value );

							count--;
							relate++;
						};

						// Little trick to recover memory
						try {
							RAISEFIELDERROR_2PARAM( ERR_INV_LITERAL_VALUE, source->GetFieldNumber(), source->GetDescrMessage(), tempstr );
						} catch( ... ) {
							delete [] tempstr;
							throw;
						};
					};
				} else
					SetDefault();
			};
		};
			break;


		default:		// Unsupported conversion
			RAISEFIELDERROR_4PARAM( ERR_INVALID_CONVERSION, source->GetFieldNumber(), source, source, this, this );
	};

	OUTFUNC()
};
 

void XCField::BLOB_move( const XCField* source, bool MoveBlanks ) {
	INFUNC( XCField::BLOB_move, owner->APPDIC );

	// first see if anything needs to be done
	if ( !source->Valid_BLOB() ) {
		if ( MoveBlanks && validblob ) {
			delete[] (long *)(buffer_offset + sizeof( long ));
			validblob = false;

			// Clear out the blob size
			memset( buffer_offset, 0, 2*sizeof( long ) );
		}
		EXITFUNC();
	}

	// Good to go, allocate space and copy
	InitBLOB( source->GetBLOBSize() );
	memcpy( *(long**)(buffer_offset + sizeof(long)), *(long**)(source->buffer_offset + sizeof(long)), GetBLOBSize() );

	OUTFUNC();
};





double XCField::ToJulian() const {
	INFUNC( XCField::ToJulian, owner->APPDIC );

	const tdata_type_defnition *source_dd = &data_type_defns[ type ];

	char date[22];
	double jday;		//	Julian date
	long err;

 	// If blank, return 0
	if (IsBlank())
		RETURNFUNC( 0 );

	// If source is julian, skip first conversion
	if (type == DATE_JULIAN)
		memcpy( &jday, buffer_offset, sizeof( double ) );
	else {
		char* source = buffer_offset;
		char* date_ptr = date;

		// Do date conversion
		bool has_date = false;

		for (int i=0; i<3; i++) {
			if (source_dd->date_form.date_type[i] != -1) {
				has_date = true;
				if (i > 0)
					*date_ptr++ = ' ';
			};

			if (i == 1 && source_dd->date_form.sep_one != 0)
				source++;
			if (i == 2 && source_dd->date_form.sep_two != 0)
				source++;

			switch( source_dd->date_form.date_type[i] ) {
				case 0:	// Year (2 char)
				case 2: // Year (A0 format)
				case 3:	// Month (MM format)
				case 6:	// Day
					memcpy( date_ptr, source, 2 );
					date_ptr += 2;
					source += 2;
					break;

				case 1:	// Year (4 char)
					memcpy( date_ptr, source, 4 );
					date_ptr += 4;
					source += 4;
					break;

				case 4:	// Month (JAN format)
					assert( false );
					break;

				case 5: // Month (January format)
					assert( false );
					break;
			};
		};

		// Special case for short dates
		if (has_date && source_dd->type_group & TYPE_SHORT_DATE) {
			// Use a leap year in case of types w/o year
			memcpy( date_ptr, "76", 2 );
			date_ptr += 2;
		};

		// Special case for timestamp
		if (type == DATE_TIMESTAMP) {
			*date_ptr++ = ' ';
			source++;
		};

		// Do time conversion
		if (source_dd->date_form.time_format > 0) {
			int len = strlen( source );
			memcpy( date_ptr, source, len );
			date_ptr += len;
		};

		// Terminator required
		*date_ptr++ = ';';
		*date_ptr++ = 0;

		owner->APPDIC->ToolsTrace( TRC_FIELD, TLV_EVERYTHING, "TextToJulian: %s  Type=%d", date, type );

		// Convert source to Julian
		long date_length = 20;
		if (!text_to_julian( date, source_dd->date_form, jday, date_length, err ))
			RAISEFIELDERROR_1PARAM( ERR_INV_DATE, GetFieldNumber(), this );
			// STATUS[6] = err
	};

	OUTFUNCRET( jday );
};




void XCField::FromGregorian( date_conv_type& date ) {
	INFUNC( XCField::FromJulian, owner->APPDIC );

	const tdata_type_defnition *target_dd = &data_type_defns[ type ];
	long date_length = 0;
	long err;

	if (!gregorian_to_text( date, target_dd->date_form, buffer_offset, date_length, err ))
		RAISEFIELDERROR_1PARAM( ERR_INV_DATE, GetFieldNumber(), this );

	OUTFUNC();
};




void XCField::FromJulian( double jul_date ) {
	INFUNC( XCField::FromJulian, owner->APPDIC );

	const tdata_type_defnition *target_dd = &data_type_defns[ type ];

	long err;

	if (type == DATE_JULIAN) {
		memcpy( buffer_offset, &jul_date, sizeof( double ) );
		EXITFUNC();
	};

	// Convert Julian to target
	owner->APPDIC->ToolsTrace( TRC_FIELD, TLV_EVERYTHING, "JulianToText: %f  Type=%d", jul_date, type );
	long date_length = 0;
	if (fabs(jul_date) < EPSILON) {
		memset( buffer_offset, allocsize, 0 );
	} else {
		if (!julian_to_text( jul_date, target_dd->date_form, buffer_offset, date_length, err ))
			RAISEFIELDERROR_1PARAM( ERR_INV_DATE, GetFieldNumber(), this );
	};

	owner->APPDIC->ToolsTrace( TRC_FIELD, TLV_EVERYTHING, "JulianToText: %s", buffer_offset );

/*
	if (type == DATE_YMD)
		M_A0_00_TO_A0( date, buffer_offset, err );
*/
	OUTFUNC();
};




void XCField::date_move( const XCField* source, bool MoveBlanks ) {
	INFUNC( XCField::date_move, owner->APPDIC )

	double jday;		//	Julian date

	// STRINGS MAY BE MOVED DIRECTLY TO DATE FIELDS AND DATE FIELDS MAY BE
	// MOVED TO STRING FIELDS.  NO VALIDATION OCCURS, AND THE DATE MAY BE
	// TRUNCATED ON THE RIGHT IF THE TARGET IS NOT AS LONG AS THE SOURCE FIELD.
	// ALL OTHER DATE MOVES ARE DONE BY CONVERTING THE DATE TO JULIAN AND THEN
	// CONVERTING IT BACK TO GREGORIAN.  IF THE SOURCE IS JULIAN, THE FIRST
	// CONVERSION IS SKIPPED AND IF THE TARGET IS JULIAN, THE LAST CONVERSION
	// IS SKIPPED.

	// If source field contains blanks, move blanks to target and leave
	if (source->IsExplicitBlank()) {
		memset( buffer_offset, 0, size );
		owner->APPDIC->ToolsTrace( TRC_FIELD, TLV_HIGH, "Date result: [%s]", buffer_offset );
		EXITFUNC();
	};

	// If source is blank, keep original value
	if (source->IsBlank()) {
		if (MoveBlanks)
			memset( buffer_offset, 0, size );
		else {
			EXITFUNC();
		};
	};

	// See if source or target is string and if so, handle it and leave
	if (source->type == STRING || source->type == BIG_STRING || type == STRING || type == BIG_STRING) {
		// Check validity of date
		if (source->type != BIG_STRING && source->type != STRING) {
			if (!source->Chk_Date( source->buffer_offset ))
				RAISEFIELDERROR_1PARAM( ERR_INV_DATE, source->GetFieldNumber(), this );
		} else
			if (!Chk_Date( source->buffer_offset )) 
				RAISEFIELDERROR_1PARAM( ERR_INV_DATE, source->GetFieldNumber(), this );

		// Move string inline
		if (source->size >= size)		// TRUNCATE
			memcpy( buffer_offset, source->buffer_offset, size );
		else {		//	Move and pad with blanks
			memcpy( buffer_offset, source->buffer_offset, size );
			memset( buffer_offset + source->size, ' ', size - source->size );
		};

		EXITFUNC();
	};

	// Convert date to julian value
	jday = source->ToJulian();

	// Convert Julian to back to Gregorian
	FromJulian( jday );

	OUTFUNC()
};




//--------------------------------------------------------------------
//                                                                    
// This subroutine handles all moves where either the source or the   
// target has a time data type.  If this is the case, the target and  
// the source must each be one of string, numeric, or time.           
//                                                                    
//--------------------------------------------------------------------
void XCField::time_move( const XCField* source, bool MoveBlanks ) {
	INFUNC( XCField::time_move, owner->APPDIC )
	
	const tdata_type_defnition *source_dd = &data_type_defns[ source->type ];
	const tdata_type_defnition *target_dd = &data_type_defns[ type ];

	char HR24[ 5 ];
	char display[ 14 ];

	// If source field contains blanks, move blanks to target and leave
	if (source->IsExplicitBlank()) {
		memset( buffer_offset, ' ', size );
		EXITFUNC();
	};

	// If blank source, just use original value
	if (source->IsBlank() && !MoveBlanks)
		EXITFUNC();

	// See if source or target is string and if so, handle it and leave
	if (source_dd->type_group == TYPE_STRING || target_dd->type_group == TYPE_STRING) {
		if (source_dd->type_group == TYPE_STRING)
			if (!Chk_Time( source->buffer_offset, source->size, type ))
				RAISEFIELDERROR_1PARAM( ERR_INV_TIME, source->GetFieldNumber(), source );

		// Move string inline
		if (source->size >= size)		// TRUNCATE
			memcpy( buffer_offset, source->buffer_offset, size );
		else {		//	Move and pad with blanks
			memcpy( buffer_offset, source->buffer_offset, size );
			memset( buffer_offset + source->size, ' ', size - source->size );
		};
		EXITFUNC();
	};



	if (source_dd->type_group & TYPE_TIME) {
		if (target_dd->type_group & TYPE_TIME) {
			TIME_SOURCE_TO_24HR( source, HR24 );
			HR24_TO_TIME_TARGET( HR24, this );
//			printf( "Result = %s  (Type=%d)\n", this->GetOffset(), this->GetType() );
		} else {		// Target is numeric
			TIME_SOURCE_TO_24HR( source, HR24 );
			HR24_TO_DISPLAY( HR24, display );
			DISPLAY_TO_NBR_TARGET( owner->APPDIC, source, this, display );
		};
	} else {
		// The source is numeric, the target is time
		NBR_SOURCE_TO_DISPLAY( owner->APPDIC, source, this, display );
		DISPLAY_TO_24HR( display, HR24 );
//			throw XCToolsError( INVALID_TIME, source );

		// IF ZERO, MAKE 0001 TO BE AT RIGHT END OF THE DAY.
		if (memcmp(HR24, "0000", 4)==0)
			memcpy(HR24, "0001", 4);

		HR24_TO_TIME_TARGET( HR24, this );
	};

	OUTFUNC()
};






//*********************************************************
//                                                         
//  Subroutine name:  DURATION'MOVE                        
//                                                         
//  This subroutine handles all moves where either the     
//  source or the target has a duration data type.  If     
//  this is the case, the source and the target must each  
//  be one of the following types:  string, numeric, or    
//  duration.                                              
//                                                         
//*********************************************************
void XCField::duration_move( const XCField* source, bool MoveBlanks ) {
	INFUNC( XCField::duration_move, owner->APPDIC )

	// If source field contains blanks, move blanks to target and leave
	if (source->IsBlank()) {
		memset( buffer_offset, ' ', size );
		EXITFUNC();
	};

	const tdata_type_defnition *source_dd = &data_type_defns[ source->type ];
	const tdata_type_defnition *target_dd = &data_type_defns[ type ];

	// See if source or target is string and if so, handle it and leave
	if (source_dd->type_group == TYPE_STRING) {
		// IF THE SOURCE IS A STRING, THEN THE TARGET MUST
		// BE A DURATION.  VALIDATE THE STRING FOR THE
		// PROPER DURATION TYPE BEFORE MOVING THE STRING.
		if (source->type == BIG_STRING || source->type == STRING) {
			if (!source->Chk_Duration( type ))
				RAISEFIELDERROR_1PARAM( ERR_INVALID_DURATION, source->GetFieldNumber(), this );
		} else {
			if (!source->Chk_Duration( source->type ))
				RAISEFIELDERROR_1PARAM( ERR_INVALID_DURATION, source->GetFieldNumber(), this );
		};

		// Move string inline
		if (source->size >= size)		// TRUNCATE
			memcpy( buffer_offset, source->buffer_offset, size );
		else {		//	Move and pad with blanks
			memset( buffer_offset, ' ', size );
			memcpy( buffer_offset, source->buffer_offset, size );
		};
		return;
	}
	
	// CASE 2:  SOURCE OR TARGET = DURATION
	if (source_dd->type_group == TYPE_DUR) {
		if (target_dd->type_group == TYPE_DUR) {
			// DURATION TO DURATION
			double value = source->DurationToDouble();
			DoubleToDuration( value );
		} else {
			// SOURCE IS DURATION AND TARGET IS NUMERIC
			double value = source->DurationToDouble();
			SetDouble( value );
		};
	} else {	// SOURCE IS NUMERIC AND TARGET IS DURATION
		DoubleToDuration( source->GetDouble( true ) );
	};	// OF SOURCE IS NUMERIC AND TARGET IS DURATION

	OUTFUNC()
};










double XCField::DurationToDouble() const {
	INFUNC( XCField::DurationToDouble, owner->APPDIC )

	double mresult = 0;
	double result;
	const char* temp = buffer_offset;
	char* stop;

	// On a blank value, return 0
	if (IsBlank())
		return 0;

	// HM types don't accept decimal points
	if (type == DUR_HM_TIME || type == DUR_HM_TIME_DISP) {
		if (strchr( temp, '.' ) || strchr( temp, ','))
			RAISEFIELDERROR_1PARAM( ERR_INVALID_DURATION, GetFieldNumber(), this );
	};
	// Grab the hour result
	double hresult = strtod( temp, &stop );

	// Display types require : between hours and minutes
	if (type == DUR_HM_TIME_DISP) {
		while (*stop && *stop == ' ')
			stop++;

		// Ensure that we have hit a colon separator
		if (*stop == ':') {
			temp = stop + 1;

			// Get rid of any whitespace at the end of the string
			// Ensure that we have 2 characters for the minutes
			int slen = strlen( temp );
			if (slen >= 2) {
				char* wspace = (char*)temp + slen - 1;
				while (wspace >= temp+2 && *wspace == ' ') {
					*wspace = 0;
					wspace--;
				};

				if (strlen( temp ) != 2)
					RAISEFIELDERROR_1PARAM( ERR_INVALID_DURATION, GetFieldNumber(), this );
			} else
				RAISEFIELDERROR_1PARAM( ERR_INVALID_DURATION, GetFieldNumber(), this );
			mresult = strtod( temp, &stop );
		} else if (*stop == 0 || *stop == ' ') {
			// This syntax is only valid if there is room left for the colon and two digits
			// for minutes, otherwise, it will create an overflow situation
			int used_length = stop - temp;
			if (used_length > GetLength() - 3)
				RAISEFIELDERROR_1PARAM( ERR_INVALID_DURATION, GetFieldNumber(), this );

			// Clear out whitespace
			char* wspace = stop + strlen( stop ) - 1;
			while (wspace >= stop && *wspace == ' ') {
				*wspace = 0;
				wspace--;
			};

			// Only accept the duration if there is nothing else trailing the value
			if (strlen( stop ) != 0)
				RAISEFIELDERROR_1PARAM( ERR_INVALID_DURATION, GetFieldNumber(), this );

			// Set the minutes result to zero
			mresult = 0;
		} else
			RAISEFIELDERROR_1PARAM( ERR_INVALID_DURATION, GetFieldNumber(), this );

		// Check minute limits
		if (mresult < 0 || mresult > 59)
			RAISEFIELDERROR_1PARAM( ERR_INVALID_DURATION, GetFieldNumber(), this );

		result = hresult + mresult / 60.0;
	} else
		result = hresult;

	// Ignore whitespace
	while (*stop && *stop == 32)
		stop++;

	// Make sure this is the last character
	if (*stop != 0)
		RAISEFIELDERROR_1PARAM( ERR_INVALID_DURATION, GetFieldNumber(), this );

	// If this is a HM type, then we need to divide by 100 to get fractional hours
	if (type == DUR_HM_TIME)
		result = result / 100.0;

	OUTFUNCRET( result );
};



void XCField::DoubleToDuration( double value ) {
	INFUNC( XCField::DoubleToDuration, owner->APPDIC )

	double h;
	double m;

	m = modf( value, &h );
	m *= 60;

	long minutes = (long)m;
	long hours = (long)h;

	switch( type ) {
		case DUR_HH_TIME:			// PACKED HOURS
			sprintf( buffer_offset, "%02ld", hours );
			break;

		case DUR_HH_TIME_DISP:		// +- HOURS.FRACTION
			sprintf( buffer_offset, "%02ld", hours );
			break;

		case DUR_HM_TIME:			// +- HOURSMM
			sprintf( buffer_offset, "%02ld%02ld", hours, minutes );
			break;

		case DUR_HM_TIME_DISP:		// +- HOURS:MM
			sprintf( buffer_offset, "%2ld:%02ld", hours, minutes );
			break;

		default:
			RAISEFIELDERROR( 1, GetFieldNumber() );
	};

//	printf( "DoubleToDuration: %f %s\n", value, buffer_offset );

	OUTFUNC();
};






//*********************************************************
//                                                         
//  Subroutine name:  FIND'FIRST'AND'LAST                  
//                                                         
//  This subroutine finds the indices of the first and     
//  and last non-blank characters in the given buffer.     
//                                                         
//  Parameters:                                            
//                                                         
//  SOURCE'BUFFER is the byte array containing the given   
//                source to be searched.                   
//                                                         
//  SOURCE'LENGTH is the integer value of the byte length  
//                of the given source buffer.              
//                                                         
//*********************************************************
void FIND_FIRST_AND_LAST( char* buffer, int length, long& FIRST_INDEX, long& LAST_INDEX ) {

	// FIND THE INDEX OF THE FIRST NON-BLANK CHARACTER
	FIRST_INDEX = 0;
	while (buffer[ FIRST_INDEX ] == ' ' && FIRST_INDEX < length)
		FIRST_INDEX++;

	// FIND THE INDEX OF THE LAST NON-BLANK CHARACTER
	LAST_INDEX = length - 1;
	while (buffer[ LAST_INDEX ] == ' ' && LAST_INDEX > 0)
		LAST_INDEX--;
};












//*********************************************************
//                                                         
//  Subroutine name:  HM'TO'HH'DISP                        
//                                                         
//  This logical subroutine converts a given HM-TIME-DUR   
//  source to duration type HH-TIME-DISP and places the    
//  result in the HH buffer.  This subroutine will return  
//  true if the actions were performed successfully and    
//  false if any errors occurred during processing.        
//                                                         
//  Parameters:                                            
//                                                         
//  SOURCE'BUFFER is the byte array containing the given   
//                HM-TIME-DUR source to be converted.      
//                                                         
//  SOURCE'LENGTH is the integer value of the byte length  
//                of the given source buffer.              
//                                                         
//*********************************************************
void XCField::HM_TO_HH_DISP( char* buffer, int length, char* HH, int HH_DEC_PLACES ) {
	const long TEMP_LEN		= 6;
	const long TEMP_LEN_M1	= 5;

	long FIRST_INDEX;	// INDEX OF FIRST NON-BLANK CHARACTER IN BUFFER
	long LAST_INDEX;	// INDEX OF LAST NON-BLANK CHARACTER IN BUFFER

	char TEMP_FRACTION[TEMP_LEN];	// TEMPORARY STORAGE FOR ASCII CONVERSION OF THE FRACTIONAL HOUR PART
	double FRACTION_HOUR;	// FRACTIONAL HOUR * (10 ^ 6)
	long INT_HOUR;		// INTEGER REPRESENTATION OF THE FRACTIONAL HOUR OF DURATION

	int
		MINUTES_OFFSET,		// OFFSET OF MINUTES IN BUFFER
		MINUTES,			// MINUTES OF DURATION
		NBR_HR_DIGITS,		// NUMBER OF HOUR DIGITS
		DIGITS_LEFT;		// NUMBER OF AVAILABLE PLACES LEFT IN THE HH BUFFER

	int status = 0;

	INFUNC( XCField::HM_TO_HH_DISP, owner->APPDIC )

	char ASCII_ZEROES[ MAX_FLD_LEN ];
	memset( ASCII_ZEROES, '0', MAX_FLD_LEN );

	// FIND INDICES OF THE FIRST AND LAST NON-BLANK
	// CHARACTERS IN THE SOURCE BUFFER

	FIND_FIRST_AND_LAST( buffer, length, FIRST_INDEX, LAST_INDEX );
  
	// CALCULATE NUMBER OF HOUR DIGITS THERE ARE
	NBR_HR_DIGITS = LAST_INDEX - FIRST_INDEX - 1;

	// VERIFY HIGH ORDER TRUNCATION WILL NOT OCCUR
	if (NBR_HR_DIGITS > HH_LENGTH)
		RAISEFIELDERROR( ERR_HIGH_ORDER_TRUNCATION, GetFieldNumber() );

	// MOVE THE HOURS INTO THE HH BUFFER

	memcpy( HH, &buffer[ FIRST_INDEX ], NBR_HR_DIGITS );

	// CALCULATE THE MINUTES OFFSET IN THE SOURCE BUFFER

	MINUTES_OFFSET = LAST_INDEX - 1;


	// CONVERT THE NONZERO MINUTES TO THE EQUIVALENT FRACTION OF AN HOUR

	// VERIFY THE HH BUFFER IS BIG ENOUGH TO MOVE THE DECIMAL POINT IN
	if (NBR_HR_DIGITS + 1 > HH_LENGTH)
		RAISEFIELDERROR( ERR_TOO_MANY_DIGITS, GetFieldNumber() );		// Just a warning

	// MOVE THE DECIMAL POINT INTO THE HH BUFFER
	HH[NBR_HR_DIGITS] = DECIMAL_POINT_CHAR;

	// IF THERE'S NOT ENOUGH SPACE LEFT IN THE HH BUFFER FOR THE DESIRED
	// NUMBER OF DECIMAL PLACES, LOW ORDER TRUNCATION MAY TAKE PLACE
	DIGITS_LEFT = HH_LENGTH - NBR_HR_DIGITS - 1;
	if (memcmp(&buffer[ MINUTES_OFFSET ], "00", 2) == 0)
		memcpy( TEMP_FRACTION, "000000", 6 );
	else {
		if (DIGITS_LEFT == 0)
			// LOW ORDER TRUNCATION SINCE MINUTES <> 0
			RAISEFIELDERROR( ERR_TOO_MANY_DIGITS, GetFieldNumber() );

		// CONVERT MINUTES TO FRACTION OF HOUR * (10 ^ 6)

		MINUTES = BINARY( &buffer[ MINUTES_OFFSET ], 2);
		FRACTION_HOUR = (float(MINUTES) / 60.0f) * pow( 10.0f, 6);
		INT_HOUR = FIXT (FRACTION_HOUR);

		// INITIALIZE THE TEMPORARY ASCII FRACTION BUFFER

		memset( TEMP_FRACTION, ' ', TEMP_LEN );

		// CONVERT THE FRACTION OF AN HOUR * (10 ^ 6) TO ASCII
		DASCII(INT_HOUR, 10, TEMP_FRACTION);
		if (float(MINUTES) < 6.0 && float(MINUTES) > 0.0) {
			// DASCII LEFT-JUSTIFIED RESULT AND TRUNCATED HIGH
			// ORDER 0 IN THE FRACTIONAL PART (FOR MINUTES)
			memcpy( &TEMP_FRACTION[ TEMP_LEN_M1 ], &TEMP_FRACTION[TEMP_LEN-2], TEMP_LEN_M1 );
			*TEMP_FRACTION = '0';
		};

		// WILL LOW ORDER TRUNCATION OCCUR?
		if ((HH_DEC_PLACES > DIGITS_LEFT) && memcmp( &TEMP_FRACTION[DIGITS_LEFT], ASCII_ZEROES, TEMP_LEN - DIGITS_LEFT ) != 0)
			status = ERR_TOO_MANY_DIGITS;

		// MOVE THE FRACTION OF AN HOUR INTO THE HH BUFFER
	};	// OF CONVERTING NONZERO MINUTES TO A FRACTION OF AN HOUR


	if (HH_DEC_PLACES <= DIGITS_LEFT)
		//  NO LOW ORDER TRUNCATION
		memcpy( &HH[ NBR_HR_DIGITS + 1 ], TEMP_FRACTION, HH_DEC_PLACES );

	else	// LOW ORDER TRUNCATION
		memcpy( &HH[ NBR_HR_DIGITS + 1 ], TEMP_FRACTION, DIGITS_LEFT );

	// SUCCESSFUL COMPLETION!!!
	if (!status)
		throw XCToolsWarning( status, 0 );

	OUTFUNC()
};





//*********************************************************
//                                                         
//  Procedure name:  DUR'SOURCE'TO'HH                      
//                                                         
//  This logical procedure moves any duration source into  
//  a given byte array HH, converting if necessary to the  
//  equivalent duration in HH-TIME-DISP format.  This      
//  procedure will return true if the actions were per-    
//  formed successfully and false if any errors occurred   
//  during processing.                                     
//                                                         
//  Parameters:                                            
//                                                         
//  SOURCE'FIELD'DEFN is the integer array containing the  
//                    field definition of the given dura-  
//                    tion source.                         
//                                                         
//  SOURCE'BUFFER     is the byte array containing the     
//                    record with the given duration       
//                    source in it.                        
//                                                         
//  HH                is a byte array which will contain   
//                    the equivalent source duration in    
//                    HH-TIME-DISP format.                 
//                                                         
//  HH'LENGTH         is the integer value of the byte     
//                    length of the given HH buffer.       
//                                                         
//  HH'DEC'PLACES     is the integer value of the number   
//                    of decimal places of accuracy that   
//                    is desired of the HH-TIME-DISP dur-  
//                    ation.                               
//                                                         
//  AC'STATUS         is an integer array which will con-  
//                    tain error status information on     
//                    exit.                                
//                                                         
//*********************************************************
void XCField::DUR_SOURCE_TO_HH( XCField* source, char* HH, int HH_LENGTH, int HH_DEC_PLACES ) {
	int status = 0;

/*
	int	SEPARATOR_INDEX;	// INDEX OF DURATION SEPARATOR
	char HM_TEMP[ MAX_FLD_LEN ];	// TEMPORARY STORAGE FOR HM-TIME-DUR TYPE
	char INTERMEDIATE[ 30 ];		// INTERMEDIATE STORAGE FOR CONVERSION ROUTINES

	// INITIALIZE ERROR STATUS
	STATUS[0] = 0;

	// INITIALIZE THE HH BUFFER
	memset( HH, ' ', HH_LENGTH );

	switch( source->GetType() ) {
		case DUR_HH_TIME_DISP:		// CASE 1 : HH'DISP ---> HH'DISP BUFFER

			// HANDLE LIKE A DISPLAY TO DISPLAY MOVE

			if (CDISPLAY_TO_ZONED( source->GetOffset(), source->GetLength(), source->GetDecPlaces(), INTERMEDIATE, 28, HH_DEC_PLACES))
				CZONED_TO_DISPLAY(INTERMEDIATE, 28, HH_DEC_PLACES, HH, HH_LENGTH, HH_DEC_PLACES);

			break;


		case DUR_HH_TIME:			// CASE 2 : HH (PACKED) ---> HH'DISP BUFFER

			// HANDLE LIKE A PACKED TO DISPLAY MOVE

			if (CPACKED_TO_ZONED( source->GetOffset(), source->GetLength(), source->GetDecPlaces(), INTERMEDIATE, 28, HH_DEC_PLACES))
				CZONED_TO_DISPLAY(INTERMEDIATE, 28, HH_DEC_PLACES, HH, HH_LENGTH, HH_DEC_PLACES);
			break;

			
		case DUR_HM_TIME:			// CASE 3 : HM ---> HH'DISP BUFFER

			if (!HM_TO_HH_DISP( source->GetOffset(), source->GetLength(), HH, HH_DEC_PLACES ))
				return false;
			break;


		default:					// CASE 4 : HM'DISP ---> HH'DISP BUFFER
			// INITIALIZE TEMPORARY HM BUFFER
			memset( HM_TEMP, ' ', MAX_FLD_LEN );

			// FIND THE DURATION SEPARATOR INDEX

			SEPARATOR_INDEX = 0;

			while (source->GetOffset()[ SEPARATOR_INDEX] != DURATION_SEP)
				SEPARATOR_INDEX++;

			// CONVERT HM'DISP TO HM
			if (SEPARATOR_INDEX != 0)
				// MOVE HOURS INTO HM'TEMP BUFFER
				memcpy( HM_TEMP, source->GetOffset(), SEPARATOR_INDEX );

			// MOVE MINUTES INTO HM'TEMP BUFFER
			memcpy( &HM_TEMP[ SEPARATOR_INDEX ], source->GetOffset() + SEPARATOR_INDEX+1, 2 );

			if (!HM_TO_HH_DISP(HM_TEMP, MAX_FLD_LEN, HH, HH_DEC_PLACES ))
				return false;
			break;
	};

*/
	// POSSIBLE SUCCESSFUL COMPLETION
	if (status == ERR_TOO_MANY_DIGITS)
		throw XCToolsWarning( status, 0 );
};























//*********************************************************
//                                                         
//  Subroutine name:  HH'DISP'TO'HM                        
//                                                         
//  This logical subroutine converts the HH-TIME-DISP      
//  duration in the HH buffer to HM-TIME-DUR type and      
//  places the result into the given buffer.  This sub-    
//  routine will return true if the actions were per-      
//  formed successfully and false if any errors occurred   
//  during processing.                                     
//                                                         
//  Parameters:                                            
//                                                         
//  BUFFER        is the byte array into which the         
//                HM-TIME-DUR duration will be placed.     
//                                                         
//  BUFFER'LENGTH is the integer value of the byte length  
//                of the given buffer.                     
//                                                         
//*********************************************************
void XCField::HH_DISP_TO_HM( char* buffer, int length, char* HH ) {
	long	first_index, last_index;
	long	minutes_index;
	long	dp_index;			// INDEX OF THE DECIMAL POINT IN BUFFER
	double	MINUTES;			// THE MINUTES EQUIVALENT OF THE FRACTIONAL PART OF THE HOUR
	long	FRACTION_HOUR;		// ASCII REPRESENTATION OF THE FRACTIONAL PART OF THE HOUR
	long	WHOLE_HOURS;		// RECALCULATION OF WHOLE HOURS WHEN MINUTES = 60
	long	MOD_HOURS;			// INTERMEDIATE RESULT OF CALC
	int		WHOLE_MINUTES;	// THE MINUTES EQUIVALENT OF THE FRACTIONAL PART OF THE HOUR
	int		MOD_LEN;		// RETURNED FROM DASCII
	bool	POWER10;		// HOURS IS POWER OF 10

	INFUNC( XCField::HH_DISP_TO_HM, owner->APPDIC )

	// INITIALIZE THE BUFFER
	memset( buffer, ' ', length );

	// FIND THE INDEX OF THE FIRST NON-BLANK CHARACTER IN THE HH BUFFER
	first_index = 0;
	while( HH[ first_index ] == ' ')
		first_index++;

	// FIND THE INDEX OF THE DECIMAL POINT IN THE HH BUFFER
	dp_index = first_index;
	if (HH[dp_index] != DECIMAL_POINT_CHAR && HH[dp_index] != ALT_DECIMAL_POINT && HH[dp_index] != ' ')
		do {
			dp_index++;
		} while (!(dp_index == HH_LENGTH || HH[ dp_index ] == ' ' || HH[ dp_index ] == DECIMAL_POINT_CHAR || HH[ dp_index ] == ALT_DECIMAL_POINT ));

	// CALCULATE THE INDEX OF THE FIRST MINUTES DIGIT
	minutes_index = dp_index - first_index;

	// VERIFY THAT THE GIVEN BUFFER IS BIG ENOUGH
	if (minutes_index + 2 > length) 
		RAISEFIELDERROR( ERR_HIGH_ORDER_TRUNCATION, GetFieldNumber() );


	// MOVE THE HOURS DIGITS INTO THE BUFFER
	memcpy( buffer, &HH[ first_index ], minutes_index );

	// IF THERE'S NO DECIMAL POINT, MOVE "00" MINUTES INTO THE BUFFER IN THE MINUTES PLACES
	if (dp_index == HH_LENGTH || HH[dp_index] == ' ')
		memcpy( &buffer[ minutes_index ], "00", 2 );
	else {		// OTHERWISE, THERE'S A FRACTIONAL HOUR PART
		// FIND THE INDEX OF THE LAST NON-BLANK DIGIT IN THE HH BUFFER
		last_index = dp_index + 1;
		if (last_index != HH_LENGTH && HH[last_index] != ' ')
			do {
				last_index++;
			} while (!(last_index == HH_LENGTH || HH[ last_index ] == ' '));
			last_index--;

			// IF THERE'S NOTHING BEHIND THE DECIMAL POINT, "00" MINUTES
			// INTO THE MINUTES DIGITS OF THE BUFFER
			if (last_index == dp_index)
				memcpy( &buffer[ minutes_index ], "00", 2 );
			else {	// CONVERSION FROM THE FRACTIONAL HOUR TO MINUTES IS NECESSARY
				// CONVERT THE FRACTIONAL HOUR TO MINUTES
				FRACTION_HOUR = DBINARY( &HH[ dp_index + 1 ], last_index - dp_index );
				MINUTES = FRACTION_HOUR * 6.0 *(pow(10.0,(int)(dp_index - last_index + 1)));

				// ROUND TO THE NEAREST MINUTE
				WHOLE_MINUTES = FIXR(MINUTES);

				// CHECK FOR 60 MINUTES
                if (WHOLE_MINUTES == 60) {
					WHOLE_MINUTES = 0;
					WHOLE_HOURS = DBINARY( &HH[first_index], minutes_index );
					if (WHOLE_HOURS >= 0)
						WHOLE_HOURS++;
					else
						WHOLE_HOURS--;

					MOD_HOURS = WHOLE_HOURS;
					if (MOD_HOURS != 0) 
						do {
							if (MOD_HOURS != 1 && MOD_HOURS % 10 == 0)
							MOD_HOURS = MOD_HOURS / 10;
						} while (!(MOD_HOURS == 1 || MOD_HOURS % 10 != 0));

					POWER10 = (MOD_HOURS == 1);
					if (POWER10) {
						// WE MUST SEE IF THE BUFFER IS BIG ENOUGH TO HOLD NUMBER
						if (minutes_index + 3 > length )
							RAISEFIELDERROR( ERR_HIGH_ORDER_TRUNCATION, GetFieldNumber() );
						minutes_index++;
					};

					MOD_LEN = DASCII(WHOLE_HOURS, 10, buffer);
					memcpy( &buffer[ minutes_index ], "00", 2 );
				} else {
					// PLACE THE ASCII EQUIVALENT OF THE MINUTES INTO THE BUFFER
					if (WHOLE_MINUTES >= 10)
						// TWO DIGIT CONVERSION
						ASCII(WHOLE_MINUTES, 10, &buffer[ minutes_index ]);
					else {
						// ONE DIGIT CONVERSION
						buffer[ minutes_index ] = '0';
						ASCII(WHOLE_MINUTES, 10, &buffer[ minutes_index + 1 ]);
					};
				};
			};	// OF CONVERSION OF FRACTIONAL HOUR TO MINUTES
	};	// OF FRACTIONAL HOUR PART

	// SUCCESSFUL COMPLETION!!!
	OUTFUNC()
};











//*********************************************************
//                                                         
//  Procedure name:  HH'TO'DUR'TARGET                      
//                                                         
//  This logical procedure moves the HH-TIME-DISP dura-    
//  tion type from the given HH buffer into the given      
//  target buffer after doing any necessary conversions    
//  to the desired duration type.  This procedure will     
//  return true if the actions were performed success-     
//  fully and false if any errors occurred during pro-     
//  cessing.                                               
//                                                         
//  Parameters:                                            
//                                                         
//  TARGET'FIELD'DEFN is the integer array containing the  
//                    field definition of the duration     
//                    target.                              
//                                                         
//  TARGET'BUFFER     is the byte array into which the     
//                    desired duration will be placed.     
//                                                         
//  HH                is the byte array which contains     
//                    the HH-TIME-DISP duration that will  
//                    be converted and moved into the      
//                    target buffer.                       
//                                                         
//  HH'LENGTH         is the integer value of the byte     
//                    length of the given HH buffer.       
//                                                         
//  HH'DEC'PLACES     is the integer value of the number   
//                    of decimal place accuracy that the   
//                    the HH-TIME-DISP duration in the HH  
//                    buffer has.                          
//                                                         
//  AC'STATUS         is an integer array which will con-  
//                    tain error status information on     
//                    exit.                                
//                                                         
//*********************************************************
void XCField::HH_TO_DUR_TARGET( XCField* target, char* HH, int HH_LENGTH, int HH_DEC_PLACES ) {

//	char HM[ MAX_FLD_LEN ];
//	char INTERMEDIATE[ 30 ];

	INFUNC( XCField::HH_TO_DUR_TARGET, owner->APPDIC )

	// Initialize error status
	int status = 0;
	
	// INITIALIZE THE TARGET BUFFER FOR ALL TYPES EXCEPT PACKED
	if (target->GetType() != DUR_HH_TIME) 
		memset( target->GetOffset(), ' ', target->GetLength() );
/*

	switch( target->GetType() ) {
		case DUR_HH_TIME_DISP:	// CASE 1 : HH'DISP BUFFER ---> HH'DISP
			// HANDLE LIKE A DISPLAY TO DISPLAY MOVE
			if (CDISPLAY_TO_ZONED(HH, HH_LENGTH, HH_DEC_PLACES, INTERMEDIATE, 28, target->GetDecPlaces() ))
				CZONED_TO_DISPLAY(INTERMEDIATE, 28, target->GetDecPlaces(), target->GetOffset(), target->GetLength(), target->GetDecPlaces());
			break;

			
		case DUR_HH_TIME:		// CASE 2 : HH'DISP BUFFER ---> HH (PACKED)
			// HANDLE LIKE A DISPLAY TO PACKED MOVE
			if (CDISPLAY_TO_ZONED(HH, HH_LENGTH, HH_DEC_PLACES, INTERMEDIATE, 28, HH_DEC_PLACES))
				CZONED_TO_PACKED(INTERMEDIATE, 28, HH_DEC_PLACES, target->GetOffset(), target->GetLength(), target->GetDecPlaces() );
			break;


		case DUR_HM_TIME:		// CASE 3 : HH'DISP BUFFER ---> HM
			if (!HH_DISP_TO_HM( target->GetOffset(), target->GetLength(), HH ))
				return false;
			break;

		default:				// CASE 4 : HH'DISP BUFFER ---> HM'DISP

			// HH'DISP BUFFER ---> HM
			if (!HH_DISP_TO_HM(HM, target->GetLength() - 1, HH))
				return false;

			// HM ---> HM'DISP
			// MOVE HOURS INTO TARGET BUFFER
			memcpy( target->GetOffset(), HM, MINUTES_INDEX );

			// MOVE DURATION SEPARATOR INTO TARGET BUFFER
			target->GetOffset()[ MINUTES_INDEX ] = DURATION_SEP;

			// MOVE MINUTES INTO TARGET BUFFER
			memcpy( target->GetOffset() + MINUTES_INDEX + 1, &HM[ MINUTES_INDEX ], 2 );
			break;
	};
*/
	// POSSIBLE SUCCESSFUL COMPLETION !!!
	if (status == ERR_TOO_MANY_DIGITS)
		RAISEFIELDERROR( status, GetFieldNumber() );

	OUTFUNC()
};












//--------------------------------------------------------------------
//                                                                    
// MOVE DATA FROM SOURCE'BUFF WHICH HAS A TIME DATA TYPE TO THE       
// TEMPORARY BUFFER HR24.                                             
//                                                                    
//--------------------------------------------------------------------
void XCField::TIME_SOURCE_TO_24HR( const XCField* source, char* HR24 ) {
	char AM_PM_LETTER;
	char* offset = source->GetOffset();

	switch( source->GetType() ) {
		case TIME_24HR:
			// Straight copy
			memcpy( HR24, offset, 4 );
			return;
			break;

		case TIME_24HR_DB:
		case TIME_24HR_DISPLAY:
		case TIME_EU_24HR_DISP:
			memcpy( HR24, offset, 2 );

			// SKIP OVER THE ":" OR "."
			memcpy( &HR24[2], offset + 3, 2 );

			return;
			break;

		case TIME_12HR:
			// MOVE THE 12 HOUR TIME IN AND THEN ADJUST FOR PM
			memcpy( HR24, offset, 4 );
			AM_PM_LETTER = offset[ 4 ];
			break;

		default:
			// THE SOURCE IS A 12 HOUR DISPLAY TYPE
			memcpy( HR24, offset, 2 );
			memcpy( &HR24[2], offset + 3, 2 );
			AM_PM_LETTER = offset[5];
			break;
	};



	if (AM_PM_LETTER == 'A' || memcmp( HR24, "12", 2 ) == 0 && AM_PM_LETTER != 'M') {
		if (AM_PM_LETTER == 'A' && memcmp( HR24, "12", 2 ) == 0) {
			memcpy( HR24, "00", 2 );
			return;
		} else {
			if (HR24[0] == '1' || HR24[1] > '7')
				HR24[0] = '2';
			else
				HR24[0] = '1';

			// ADD 2 TO THE SECOND DIGIT OF THE HOURS >>                         04204000
			HR24[1] += 2;
			if (HR24[1] > '9')
				HR24[1] -= 10;
		};
	};
};











//--------------------------------------------------------------------
//                                                                    
// MOVE A TIME FROM THE TEMPORARY BUFFER HR24 TO THE TARGET BUFFER.   
// T'TYPE IS IN THE RANGE 20 ... 23 (I.E. IT IS A TIME).              
//                                                                    
//--------------------------------------------------------------------
void XCField::HR24_TO_TIME_TARGET( char* HR24, XCField* target ) {
	INFUNC( XCField::HR24_TO_TIME_TARGET, owner->APPDIC )

	owner->APPDIC->ToolsTrace( TRC_FIELD, TLV_LOW, "HR24 = %s", HR24 );
	const char DELTA_DIGIT[ 11 ] = "8901234567";

	char AM_PM_LETTER;
	char* offset = target->GetOffset();

	if (HR24[0] == ' ')
		HR24[0] = '0';
	if (HR24[1] == ' ')
		HR24[1] = '0';

	switch( target->GetType() ) {
		case TIME_24HR:
		case TIME_12HR:
			memcpy( offset, HR24, 4 );
			if (target->GetType() == TIME_24HR)
				return;
			break;

		case TIME_12HR_DISPLAY:
		case TIME_24HR_DISPLAY:
			memcpy( offset, HR24, 2 );
			offset[2] = ':';
			memcpy( &offset[3], &HR24[2], 2 );
			if (target->GetType() == TIME_24HR_DISPLAY)
				return;
			break;

		case TIME_EU_12HR_DISP:
		case TIME_EU_24HR_DISP:
			memcpy( offset, HR24, 2 );
			offset[2] = '.';
			memcpy( &offset[3], &HR24[2], 2 );
			if (target->GetType() == TIME_EU_24HR_DISP)
				return;
			break;

		case TIME_24HR_DB:
			memcpy( offset, HR24, 2 );
			offset[2] = ':';
			memcpy( &offset[3], &HR24[2], 2 );
			offset[5] = ':';
			offset[6] = '0';
			offset[7] = '0';
			offset[8] = 0;
			return;
			break;

		default:
			printf( "INVALID TIME TYPE: %d", target->GetType() );
			RAISEFIELDERROR_4PARAM( ERR_INVALID_CONVERSION, target->GetFieldNumber(), target, target, target, target );
	};


	// WE MUST PUT IN "A", "P", "N" OR "M" FOR THE 12 HOUR TIME.
	if (memcmp( HR24, "2400", 4 ) == 0 || memcmp( HR24, "1200", 4 ) == 0) {
		if (memcmp( HR24, "2400", 4 )==0) {
			AM_PM_LETTER = 'M';
			memcpy( offset, "12", 2 );
		} else
			AM_PM_LETTER = 'N';
	} else {
		if ((HR24[0] == '1' && HR24[1] >= '2') || HR24[0] == '2') {
			// WE HAVE A PM TIME
			AM_PM_LETTER = 'P';
			if (memcmp(HR24, "12", 2) == 0) {
				// NOW WE MUST SUBTRACT 12 FROM THE HOURS
				// FIRST WE DO THE TENS DIGIT.
				if (HR24[0] == '1' || HR24[1] < '2')
					*offset = '0';
				else
					*offset = '1';

				offset[1] = DELTA_DIGIT[ HR24[1] - '0' ];
			};
		} else {
			// WE HAVE AN AM TIME
			AM_PM_LETTER = 'A';
			if (memcmp(HR24, "00", 2)==0)
				memcpy( offset, "12", 2 );
		};
	};


	if (target->GetType() == TIME_12HR)
		offset[4] = AM_PM_LETTER;
	else
		offset[5] = AM_PM_LETTER;

	OUTFUNC()
};












//--------------------------------------------------------------------
//                                                                    
// THIS PROCEDURE CONVERTS DATA IN TIME'24HR TO DISPLAY FORMAT.       
// DISPLAY IS 12 BYTES LONG.  THERE ARE 2 DIGITS BEFORE THE DECIMAL   
// POINT AND 9 AFTER THE DECIMAL POINT.                               
//                                                                    
//--------------------------------------------------------------------
void XCField::HR24_TO_DISPLAY( char* HR24, char* DISPLAY ) {
/*
	long temp_dbl;
	long i;
	char INTERMEDIATE[ 30 ];

	// MOVE THE HOURS INTO THE INTEGER PART OF THE DISPLAY
	memcpy( DISPLAY, HR24, 2 );

	// ADD THE DECIMAL POINT
	DISPLAY[2] = '.';

	// CONVERT THE MINUTES TO LONG
	temp_dbl = (HR24[2]-'0') * 10 + (HR24[3]-'0');

	// MULTIPLY BY 10**9 AND DIVIDE BY 60 TO GET FRACTIONAL PART OF HOUR
	// MULTIPLIED BY 10**9.
	temp_dbl = ((10000000 * temp_dbl) / 60) * 100;

	// CONVERT BACK TO DISPLAY TYPE AND PAD WITH LEADING ZEROES TO
	// LENGTH 9 AND PUT AFTER THE DECIMAL POINT IN DISPLAY.
	if (!CDINTEGER_TO_PACKED( temp_dbl, 8, INTERMEDIATE, 14, 0))
		return false;

	if (!CPACKED_TO_ZONED(INTERMEDIATE, 14, 0, INTERMEDIATE, 28, 0))
		return false;

	if (!CZONED_TO_DISPLAY(INTERMEDIATE, 28, 0, DISPLAY[3], 9, 0))
		return false;

	i = 3;
	while (DISPLAY[i] == ' ') {
		DISPLAY[i] = '0';
		i++;
	};
*/

};








//--------------------------------------------------------------------
//                                                                    
// MOVE A NUMBER FROM DISPLAY TO HR24.  DISPLAY IS 12 BYTES LONG      
// WITH 9 DIGITS AFTER THE DECIMAL POINT.  HR24 IS 4 BYTES LONG.      
//                                                                    
//--------------------------------------------------------------------
void XCField::DISPLAY_TO_24HR( char* DISPLAY, char* HR24) {
	long i;
	char minutes;

	// MOVE THE HOURS IN
	memcpy( HR24, DISPLAY, 2 );

	if (HR24[0] == ' ')
		HR24[0] = '0';
	if (HR24[1] == ' ')
		HR24[1] = '0';

	// CALCULATE THE MINUTES TO THE NEAREST MINUTE.

	minutes = 0;
	for (i=3; i<=5; i++)
		minutes = minutes * 10 + DISPLAY[i] - '0';
	minutes = (char)((long)(minutes * 60 + 500) / 1000);

	// MOVE THE MINUTES IN

	HR24[2] = '0' + minutes / 10;
	HR24[3] = '0' + minutes % 10;
};









//--------------------------------------------------------------------
//                                                                    
// THIS SUBROUTINE CONVERTS THE NBR IN DISPLAY TO THE TARGET TYPE AND 
// PLACES THE RESULT IN TARGET'BUFF.                                  
//                                                                    
//--------------------------------------------------------------------
void XCField::DISPLAY_TO_NBR_TARGET( XCAppInterface* appdic, const XCField* source, XCField* target, char* DISPLAY ) {
	XCField* new_field = NULL;

//	new_field = appdic->CreateDynamiXCField( DISPLAY, 12, 9, DISPLAY_NUM, 12, source->GetFlags() );

	try {
		target->MoveField( new_field, true );
	} catch( XCToolsWarning& warning ) {
		if (warning.GetErrorNumber() != ERR_TOO_MANY_DIGITS) {
			delete new_field;
			throw warning;
		};
	} catch( XCToolsError& error ) {
		delete new_field;
		throw error;
	};
};






//--------------------------------------------------------------------
//                                                                    
// CONVERT THE SOURCE INPUT TO DISPLAY'NBR FORMAT AND PUT THE RESULT  
// IN DISPLAY.  RETURN FALSE IF THE NUMBER IS .LT. 0 OR .GT. 24.      
//                                                                    
//--------------------------------------------------------------------
void XCField::NBR_SOURCE_TO_DISPLAY( XCAppInterface* appdic, const XCField* source, XCField* target, char* DISPLAY ) {
	XCField* new_field = NULL;

	INFUNC( XCField::NBR_SOURCE_TO_DISPLAY, owner->APPDIC )

//	new_field = appdic->CreateDynamiXCField( DISPLAY, 12, 9, DISPLAY_NUM, 12, target->GetFlags() );

	try {
		new_field->MoveField( source, true );
	} catch( XCToolsWarning& warning ) {
		// Just ignore the warning for too many digits
		if (warning.GetErrorNumber() != ERR_TOO_MANY_DIGITS) {
			delete new_field;
			throw;
		};
	};


	if (DISPLAY[0] == '-' || DISPLAY[1] == '-' ||
		DISPLAY[0] > '2' && DISPLAY[0] != ' ' ||
		DISPLAY[0] == '2' && DISPLAY[1] > '4' ||
		memcmp( DISPLAY, "24", 2 ) == 0 && memcmp( DISPLAY, "24.000000000", 10 ) != 0) {

		delete new_field;

		RAISEFIELDERROR_1PARAM( ERR_INV_TIME, source->GetFieldNumber(), source );
	};


	delete new_field;

	OUTFUNC()
};
